package cn.yaboya.basic.service.Impl;

import cn.yaboya.basic.constant.BaseConstants;
import cn.yaboya.basic.exception.BusinessException;
import cn.yaboya.basic.service.ILoginService;
import cn.yaboya.basic.utils.*;
import cn.yaboya.basic.vo.LoginVo;
import cn.yaboya.basic.vo.SmsCodeJudgeVo;
import cn.yaboya.basic.vo.WechatVo;
import cn.yaboya.org.domain.Wxuser;
import cn.yaboya.org.mapper.WxuserMapper;
import cn.yaboya.user.domain.Logininfo;
import cn.yaboya.user.domain.User;
import cn.yaboya.user.mapper.LogininfoMapper;
import cn.yaboya.user.service.IUserService;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private IUserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private WxuserMapper wxuserMapper;



    /**
    * @Title: account
    * @Description: 登录业务判断
    * @Author: LT
    * @Version: 1.0
    * @Date:  2022/7/8 0:02
    * @Parameters: [vo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Transactional
    public Map<String, Object> account(LoginVo vo) {
        String username = vo.getUsername();
        String password = vo.getPassword();
        Integer type = vo.getType();

        // 判断参数为空
        if(StringUtils.isEmpty(username) ||
                StringUtils.isEmpty(password) ||
                StringUtils.isEmpty(type)) {
            throw new BusinessException("输入有误 请重新输入");
        }
        // 2.根据账号&type查询用户，字段对应有三个username or phone or email and type
        Logininfo logininfo = logininfoMapper.loadByUsernameAndType(username, type);
        // 3.判断账号是否存在，不存在抛出异常
        if (logininfo == null){
            throw new BusinessException("账号密码错误，请重新输入！");
        }
        String passwordTmp  = MD5Utils.encrypByMd5(logininfo.getSalt() + password);
        // 加盐后判断密码是否正确
        if (!logininfo.getPassword().equals(passwordTmp)) {
            throw new BusinessException("账号密码错误，请重新输入！");
        }
        // 5.生成UUID作为key
        String token = UUID.randomUUID().toString();
        // 6.将uuid&&logininfo设置到redis中，设置30分钟有效期
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
        // 7.logininfo 敏感信息设置为空 作为value 返回给前端
        logininfo.setSalt("");
        logininfo.setPassword("");
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo", logininfo);
        return map;

    }

    @Override
    public Map<String, Object> phone(LoginVo vo) {
        final String phone = vo.getPhone();
        final Integer type = vo.getType();
        String smsCodeValue = vo.getSmsCodeValue();
        final String imageCodeKey = vo.getImageCodeKey();
        final String imageCodeValue = vo.getImageCodeValue();


        // 判断参数为空
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(type)) {
            throw new BusinessException("输入有误 请重新输入");
        }

        // 2.判断图片验证码不为空
        if(StringUtils.isEmpty(imageCodeValue)){
            throw new BusinessException("图片验证码不能为空");

        }
        // 3
        // 校验图像验证码在Redis 是否存在或者过期
        // 查找Redis
        final Object imageCodeTmp = redisTemplate.opsForValue().get(imageCodeKey);
        // 3.2
        if (imageCodeTmp == null) {
            throw new BusinessException("验证码为空 请重新输入");
        }
        // 3.3 查询到 验证码 且存在 判断用户输入和查找到的是否一直
        if (!imageCodeTmp.toString().equalsIgnoreCase(imageCodeValue)) {
            throw new BusinessException("验证码错误 请重新输入");
        }



        // 3.判断手机验证码不为空
        if(StringUtils.isEmpty(smsCodeValue)){

            throw new BusinessException("手机验证码不能为空");
        }

        // 3判断注册手机号和获取手机号验证码时的号码是否一致
        String smsCodeKey = BaseConstants.VerifyCodeConstant.BUSINESS_REGISTER_PREFIX + phone;

        // 3.2根据k值(传过来的手机号业务建 判断之前是否有过存在3分种没)获取值，如果获取不到就代表手机号验证码错误或已过期 代表不是本人或这过期
        Object  smsCodeValueTmp = redisTemplate.opsForValue().get(smsCodeKey);
        // 代表已经过期
        if (smsCodeValueTmp == null){
            throw new BusinessException("验证码已过期，请重新输入");
        }
        // 4判断短信验证的号码是否一致
        // 4.1获取Reids短信验证码
        String[] split = smsCodeValueTmp.toString().split(":");
        String smsCode = split[0];
        // 4.2判断是否存在不存在则已过期
        if(!smsCode.equalsIgnoreCase(smsCodeValue)){
            throw new BusinessException("验证码错误，请重新输入");
        }

        // 2.根据账号&type查询用户，字段对应有三个username or phone or email and type
        Logininfo logininfo = logininfoMapper.loadByUsernameAndType(phone, type);
        // 3.判断账号是否存在，不存在抛出异常
        if (logininfo == null){
            throw new BusinessException("电话错误，请重新输入！");
        }

        // 5.生成UUID作为key
        String token = UUID.randomUUID().toString();
        // 6.将uuid&&logininfo设置到redis中，设置30分钟有效期
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
        // 7.logininfo 敏感信息设置为空 作为value 返回给前端
        logininfo.setSalt("");
        logininfo.setPassword("");
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo", logininfo);
        return map;

    }

    @Override
    public AjaxResult wechat(WechatVo vo) {
        String code = vo.getCode();
        if (StringUtils.isEmpty(code)) {
            throw new BusinessException("参数为空，请重新传达");
        }

        // 1.拼接获取地址
        String tokenUrl = BaseConstants.WechatConstant.TOKEN_URL
                .replace("APPID", BaseConstants.WechatConstant.APPID)
                .replace("SECRET", BaseConstants.WechatConstant.SECRET)
                .replace("CODE", code);
        // 2. 将拼接好的地址通过工具类发送get请求
        String jsonStr = HttpUtil.httpGet(tokenUrl);

        // 将得到的字符串转化为json
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");

        // 根据openId 查询t_wxuser表数据是否存在
        Wxuser wxuser = wxuserMapper.loadByOpenId(openId);
        // 判断是否存在 存在则代表已经绑定了平台
        if (wxuser != null && wxuser.getUserId() != null) {
            // - 根据wxuser中的userid查询logininfo信息
            Logininfo logininfo = logininfoMapper.loadByUserId(wxuser.getUserId());
            // 生成UUID作为token存入到redis中设置有效期30分钟
            String token = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(
                    token,
                    logininfo,
                    30,
                    TimeUnit.MINUTES
            );
            // 5.将用户敏感信息置空
            logininfo.setPassword("");
            logininfo.setSalt("");
            // 6.并设置token和用户信息到map中
            HashMap<String, Object> map = new HashMap<>();
            map.put("token", token);
            map.put("logininfo", logininfo);
            return AjaxResult.me().setResultObj(map);
        }
        // 如果t_user 表不存在 则代表该用户没有绑定 且吧获取到的 access_token 和 openId返回给前端
        StringBuffer append = new StringBuffer()
                .append("?accessToken=")
                .append(accessToken)
                .append("&openId=")
                .append(openId);
        return AjaxResult.me().setSuccess(false).setResultObj(append);
    }

    @Override
    @Transactional
    public HashMap<String, Object> binder(WechatVo vo) throws IOException {
        String phone = vo.getPhone();
        String accessToken = vo.getAccessToken();
        String openId = vo.getOpenId();
        String verifyCode = vo.getVerifyCode();


        if (StringUtils.isEmpty(phone) ||
                StringUtils.isEmpty(accessToken) ||
                StringUtils.isEmpty(openId) ||
                StringUtils.isEmpty(verifyCode) ){
            throw new BusinessException("参数为空，请重新传达");
        }


        // 1.判断手机号不为空
        if(StringUtils.isEmpty(phone)){
            throw new BusinessException("手机号不能为空");
        }

        // 3.判断手机验证码不为空
        if(StringUtils.isEmpty(verifyCode)){

            throw new BusinessException("手机验证码不能为空");
        }

        // 3判断注册手机号和获取手机号验证码时的号码是否一致  这个是设置登录时
        String phoneCodeKey = BaseConstants.VerifyCodeConstant.BUSINESS_BINDER_KEY + phone;
        // 3.2根据k值(传过来的手机号业务建 判断之前是否有过存在3分种没)获取值，如果获取不到就代表手机号验证码错误或已过期 代表不是本人或这过期
        Object  smsCodeValueTmp = redisTemplate.opsForValue().get(phoneCodeKey);

        // 代表已经过期
        if (smsCodeValueTmp == null){
            throw new BusinessException("验证码已过期，请重新输入");
        }
        // 4判断短信验证的号码是否一致
        // 4.1获取Reids短信验证码
        String[] split = smsCodeValueTmp.toString().split(":");
        String smsCode = split[0];
        // 4.2判断是否存在不存在则已过期
        if(!smsCode.equalsIgnoreCase(verifyCode)){
            throw new BusinessException("验证码错误，请重新输入");
        }



        User user = userService.loadByPhone(phone);
        // 5.外层创建Logininfo对象，在if中赋值，用于等会儿最后返回给前端
        Logininfo logininfo = null;

        if (user == null){
            user = initUser(vo);
            userService.add(user);
            logininfo = user2Logininfo(user);
            logininfoMapper.add(logininfo);
            // 4.将logininfo主键信息设置到user中
            user.setLogininfoId(logininfo.getId());
            // 5.更新user对象信息
            userService.update(user);
        } else {
            // 查询的到用户说明此微信用户之前使用此手机号创建过账号，那么我们查询出此用户的logininfo信息
            logininfo = logininfoMapper.loadByUserId(user.getId());
        }
        // 7.根据accessToken+openId获取微信个人信息
        String wxUserUrl = BaseConstants.WechatConstant.WXUSER_URL
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        String wxUserJsonStr = HttpUtil.httpGet(wxUserUrl);

        // 因为通过accessTokne 和openID 发送请求可以得到微信用户信息 且返回的数据字段与t_wxuser一致
        // 所以可以直接使用JsonObject对象的parseObject方法，根据相同的字段名称赋值创建对象
        Wxuser wxuser = JSONObject.parseObject(wxUserJsonStr, Wxuser.class);

        wxuser.setUserId(user.getId());
        wxuserMapper.add(wxuser);

        // 10.生成token保存到redis中，并返回前端
        // 生成UUID作为token存入到redis中设置有效期30分钟
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(
                token,
                logininfo,
                30,
                TimeUnit.MINUTES
        );
        // 将用户敏感信息置空
        logininfo.setPassword("");
        logininfo.setSalt("");
        // 并设置token和用户信息到map中
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo", logininfo);
        return map;

    }


    private User initUser(WechatVo vo) throws IOException {
        String phone = vo.getPhone();
        User user = new User();

        user.setUsername(phone);
        user.setPhone(phone);

        String initPwd = "123456";
//        SmsUtils.sendMsg(phone, "初始密码"+ initPwd);

        String salt = StrUtils.getComplexRandomString(32);
        String password = MD5Utils.encrypByMd5(initPwd + salt);
        user.setSalt(salt);
        user.setPassword(password);
        return user;
    }

    /**
     * @Title: user2Logininfo
     * @Description: 设置logininfo对象数据
     * @Author: LT
     * @Version: 1.0
     * @Date:  2022/7/5 14:40
     * @Parameters: [user]
     * @Return cn.yaboya.user.domain.Logininfo
     */
    public Logininfo user2Logininfo(User user){
        // 利用工具类拷贝到Logininfo中
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user, logininfo);
        // 设置数据类型为用户
        logininfo.setType(1);
        return logininfo;
    }


}
